/*
 * Copyright (C) 2011 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */
package gov.nasa.worldwind.render;

import gov.nasa.worldwind.Locatable;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.cache.GpuResourceCache;
import gov.nasa.worldwind.exception.WWRuntimeException;
import gov.nasa.worldwind.geom.Matrix;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Rect;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.pick.PickSupport;
import gov.nasa.worldwind.terrain.SectorGeometryList;
import gov.nasa.worldwind.util.Logging;
import gov.nasa.worldwind.util.WWMath;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Iterator;
import java.util.logging.Level;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.opengl.GLES20;

/**
 * IconRenderer processes collections of
 * {@link gov.nasa.worldwind.render.WWIcon} instances for picking and rendering.
 * IconRenderer applies batch processing techniques to improve the runtime
 * performance of picking and rendering large collections of icons.
 * <p/>
 * During the draw pass, IconRenderer records feedback information for each
 * WWIcon which has the property key
 * {@link gov.nasa.worldwind.avlist.AVKey#FEEDBACK_ENABLED} set to
 * <code>true</code>. IconRenderer does not record any feedback information
 * during the pick pass. When feedback is enabled, IconRenderer puts properties
 * which describe how each WWIcon has been processed in key-value pairs attached
 * to the WWIcon. Any of these properties may be null, indicating that
 * processing of the WWIcon was terminated before this information became
 * available. The feedback properties for WWIcon are as follows:
 * <table>
 * <tr>
 * <th>Key</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>{@link gov.nasa.worldwind.avlist.AVKey#FEEDBACK_REFERENCE_POINT}</td>
 * <td>The icon's reference point in model coordinates.</td>
 * </tr>
 * <tr>
 * <td>{@link gov.nasa.worldwind.avlist.AVKey#FEEDBACK_SCREEN_BOUNDS}</td>
 * <td>The icon's bounding rectangle in screen coordinates.</td>
 * </tr>
 * </table>
 * 
 * @author tag
 * @version $Id: IconRenderer.java 1 2011-07-16 23:22:47Z dcollins $
 */
public class IconRenderer {
	protected static final String VERTEX_SHADER_PATH_TEXTURE = "shaders/IconLayerTexture.vert";
	protected static final String FRAGMENT_SHADER_PATH_TEXTURE = "shaders/IconLayerTexture.frag";
	protected static final String VERTEX_SHADER_PATH_COLOR = "shaders/IconLayerColor.vert";
	protected static final String FRAGMENT_SHADER_PATH_COLOR = "shaders/IconLayerColor.frag";
	protected Object programTextureKey = new Object();
	protected Object programColorKey = new Object();
	protected Pedestal pedestal;
	protected boolean horizonClippingEnabled = false;
	protected boolean viewClippingEnabled = true;
	protected boolean pickFrustumClippingEnabled = true;
	protected boolean alwaysUseAbsoluteElevation = false;
	// protected OGLStackHandler oglStackHandler = new OGLStackHandler();
	protected boolean allowBatchPicking = true;

	protected PickSupport pickSupport = new PickSupport();
	protected Matrix projection;

	public IconRenderer() {
	}

	public Pedestal getPedestal() {
		return pedestal;
	}

	public void setPedestal(Pedestal pedestal) {
		this.pedestal = pedestal;
	}

	/**
	 * Indicates whether horizon clipping is performed.
	 * 
	 * @return <code>true</code> if horizon clipping is performed, otherwise
	 *         <code>false</code>.
	 * 
	 * @see #setHorizonClippingEnabled(boolean)
	 */
	public boolean isHorizonClippingEnabled() {
		return horizonClippingEnabled;
	}

	/**
	 * Indicates whether to render icons beyond the horizon. If view culling is
	 * enabled, the icon is also tested for view volume inclusion. The default
	 * is <code>false</code>, horizon clipping is not performed.
	 * 
	 * @param horizonClippingEnabled
	 *            <code>true</code> if horizon clipping should be performed,
	 *            otherwise <code>false</code>.
	 * 
	 * @see #setViewClippingEnabled(boolean)
	 */
	public void setHorizonClippingEnabled(boolean horizonClippingEnabled) {
		this.horizonClippingEnabled = horizonClippingEnabled;
	}

	/**
	 * Indicates whether view volume clipping is performed.
	 * 
	 * @return <code>true</code> if view volume clipping is performed, otherwise
	 *         <code>false</code>.
	 * 
	 * @see #setViewClippingEnabled(boolean)
	 */
	public boolean isViewClippingEnabled() {
		return viewClippingEnabled;
	}

	/**
	 * Indicates whether to render icons outside the view volume. This is
	 * primarily to control icon visibility beyond the far view clipping plane.
	 * Some important use cases demand that clipping not be performed. If
	 * horizon clipping is enabled, the icon is also tested for horizon
	 * clipping. The default is <code>false</code>, view volume clipping is not
	 * performed.
	 * 
	 * @param viewClippingEnabled
	 *            <code>true</code> if view clipping should be performed,
	 *            otherwise <code>false</code>.
	 * 
	 * @see #setHorizonClippingEnabled(boolean)
	 */
	public void setViewClippingEnabled(boolean viewClippingEnabled) {
		this.viewClippingEnabled = viewClippingEnabled;
	}

	/**
	 * Indicates whether picking volume clipping is performed.
	 * 
	 * @return <code>true</code> if picking volume clipping is performed,
	 *         otherwise <code>false</code>.
	 * 
	 * @see #setPickFrustumClippingEnabled(boolean)
	 */
	public boolean isPickFrustumClippingEnabled() {
		return pickFrustumClippingEnabled;
	}

	/**
	 * Indicates whether to render icons outside the picking volume when in pick
	 * mode. This increases performance by only drawing the icons within the
	 * picking volume when picking is enabled. Some important use cases demand
	 * that clipping not be performed. The default is <code>false</code>,
	 * picking volume clipping is not performed.
	 * 
	 * @param pickFrustumClippingEnabled
	 *            <code>true</code> if picking clipping should be performed,
	 *            otherwise <code>false</code>.
	 */
	public void setPickFrustumClippingEnabled(boolean pickFrustumClippingEnabled) {
		this.pickFrustumClippingEnabled = pickFrustumClippingEnabled;
	}

	protected static boolean isIconValid(WWIcon icon, boolean checkPosition) {
		if (icon == null || icon.getImageTexture() == null)
			return false;

		// noinspection RedundantIfStatement
		if (checkPosition && icon.getPosition() == null)
			return false;

		return true;
	}

	/**
	 * Indicates whether an icon's elevation is treated as an offset from the
	 * terrain or an absolute elevation above sea level.
	 * 
	 * @return <code>true</code> if icon elevations are treated as absolute,
	 *         <code>false</code> if they're treated as offsets from the
	 *         terrain.
	 */
	public boolean isAlwaysUseAbsoluteElevation() {
		return alwaysUseAbsoluteElevation;
	}

	/**
	 * Normally, an icon's elevation is treated as an offset from the terrain
	 * when it is less than the globe's maximum elevation. Setting
	 * #setAlwaysUseAbsoluteElevation to <code>true</code> causes the elevation
	 * to be treated as an absolute elevation above sea level.
	 * 
	 * @param alwaysUseAbsoluteElevation
	 *            <code>true</code> to treat icon elevations as absolute,
	 *            <code>false</code> to treat them as offsets from the terrain.
	 */
	public void setAlwaysUseAbsoluteElevation(boolean alwaysUseAbsoluteElevation) {
		this.alwaysUseAbsoluteElevation = alwaysUseAbsoluteElevation;
	}

	/**
	 * Indicates whether icons are picked as a batch and therefore a
	 * {@link gov.nasa.worldwind.event.SelectEvent} will contain only one icon
	 * from a given layer. Batch picking is much faster than individual picking,
	 * so this attribute should be used judiciously.
	 * 
	 * @return true if batch picking is allowed, otherwise false.
	 * 
	 * @see #setAllowBatchPicking(boolean)
	 */
	public boolean isAllowBatchPicking() {
		return this.allowBatchPicking;
	}

	/**
	 * Specifies whether batch picking is allowed. If so, a
	 * {@link gov.nasa.worldwind.event.SelectEvent} from a layer will contain
	 * only one icon even if several overlapping icons are at the pick point.
	 * Batch picking is much faster than individual picking so the default value
	 * is true.
	 * 
	 * @param allowBatchPicking
	 *            true if batch picking is allowed, otherwise false.
	 */
	public void setAllowBatchPicking(boolean allowBatchPicking) {
		this.allowBatchPicking = allowBatchPicking;
	}

	@SuppressWarnings({ "UnusedDeclaration" })
	public void pick(DrawContext dc, Iterable<? extends WWIcon> icons, Point pickPoint, Layer layer) {
		this.drawMany(dc, icons, layer);
	}

	public void render(DrawContext dc, Iterable<? extends WWIcon> icons) {
		this.drawMany(dc, icons, null);
	}

	protected void drawMany(DrawContext dc, Iterable<? extends WWIcon> icons, Layer layer) {
		if (dc == null) {
			String msg = Logging.getMessage("nullValue.DrawContextIsNull");
			Logging.error(msg);
			throw new IllegalArgumentException(msg);
		}

		if (dc.getVisibleSector() == null)
			return;

		SectorGeometryList geos = dc.getSurfaceGeometry();
		// noinspection RedundantIfStatement
		if (geos == null)
			return;

		if (icons == null) {
			String msg = Logging.getMessage("nullValue.IconIterator");
			Logging.error(msg);
			throw new IllegalArgumentException(msg);
		}

		Iterator<? extends WWIcon> iterator = icons.iterator();

		if (!iterator.hasNext())
			return;

		double horizon = WWMath.computeHorizonDistance(dc.getGlobe(),
				dc.getView().getEyePosition(dc.getGlobe()).elevation);

		while (iterator.hasNext()) {
			WWIcon icon = iterator.next();
			if (!isIconValid(icon, true)) {
				// Record feedback data for this WWIcon if feedback is enabled.
				if (icon != null)
					this.recordFeedback(dc, icon, null, null);

				continue;
			}

			if (!icon.isVisible()) {
				// Record feedback data for this WWIcon if feedback is enabled.
				this.recordFeedback(dc, icon, null, null);

				continue;
			}

			// Determine Cartesian position from the surface geometry if the
			// icon is near the surface,
			// otherwise draw it from the globe.
			Position pos = icon.getPosition();
			Vec4 iconPoint = null;
			if (pos.elevation < dc.getGlobe().getMaxElevation() && !this.isAlwaysUseAbsoluteElevation()) {
				// iconPoint =
				// dc.getSurfaceGeometry().getSurfacePoint(icon.getPosition());
				double elevation = dc.getGlobe().getElevation(pos);
				iconPoint = dc.getGlobe()
						.computePointFromPosition(new Position(pos.latitude, pos.longitude, elevation));
			}
			if (iconPoint == null)
				iconPoint = dc.getGlobe().computePointFromPosition(icon.getPosition());

			double eyeDistance = icon.isAlwaysOnTop() ? 0 : dc.getView().getEyePoint().distanceTo3(iconPoint);

			if (this.isHorizonClippingEnabled() && eyeDistance > horizon) {
				// Record feedback data for this WWIcon if feedback is enabled.
				this.recordFeedback(dc, icon, iconPoint, null);

				continue; // don't render horizon-clipped icons
			}

			// If enabled, eliminate icons outside the view volume. Primarily
			// used to control icon visibility beyond
			// the view volume's far clipping plane.
			if (this.isViewClippingEnabled() && !dc.getView().getFrustumInModelCoordinates().contains(iconPoint)) {
				// Record feedback data for this WWIcon if feedback is enabled.
				this.recordFeedback(dc, icon, iconPoint, null);

				continue; // don't render frustum-clipped icons
			}

			// The icons aren't drawn here, but added to the ordered queue to be
			// drawn back-to-front.
			dc.addOrderedRenderable(new OrderedIcon(icon, iconPoint, layer, eyeDistance, horizon));

			if (icon.isShowToolTip())
				this.addToolTip(dc, icon, iconPoint);
		}
	}

	protected void addToolTip(DrawContext dc, WWIcon icon, Vec4 iconPoint) {
		if (icon.getToolTipFont() == null && icon.getToolTipText() == null)
			return;

		Vec4 screenPoint = new Vec4();
		boolean success = dc.getView().project(iconPoint, screenPoint);
		if (!success)
			return;

		if (icon.getToolTipOffset() != null)
			screenPoint = screenPoint.add3(icon.getToolTipOffset());

		OrderedText tip = new OrderedText(icon.getToolTipText(), icon.getToolTipFont(), screenPoint,
				icon.getToolTipTextColor(), 0d);
		dc.addOrderedRenderable(tip);
	}

	protected class OrderedText implements OrderedRenderable {
		protected Paint font;
		protected String text;
		protected Vec4 point;
		protected double eyeDistance;
		protected Point pickPoint;
		protected Layer layer;
		protected int color;
		private ToolTipRenderer tr;

		public OrderedText(String text, Paint font, Vec4 point, int color, double eyeDistance) {
			this.text = text;
			this.font = font;
			this.point = point;
			this.eyeDistance = eyeDistance;
			this.color = color;
		}

		public OrderedText(String text, Paint font, Vec4 point, Point pickPoint, Layer layer, double eyeDistance) {
			this.text = text;
			this.font = font;
			this.point = point;
			this.eyeDistance = eyeDistance;
			this.pickPoint = pickPoint;
			this.layer = layer;
		}

		public double getDistanceFromEye() {
			return this.eyeDistance;
		}

		public void render(DrawContext dc) {
			ToolTipRenderer toolTipRenderer = this.getToolTipRenderer(dc);
			toolTipRenderer.render(dc, this.text, (int) this.point.x, (int) this.point.y);
		}

		public void pick(DrawContext dc, Point pickPoint) {
		}

		@SuppressWarnings({ "UnusedDeclaration" })
		protected ToolTipRenderer getToolTipRenderer(DrawContext dc) {
			if (tr == null) {
				tr = (this.font != null) ? new ToolTipRenderer(this.font) : new ToolTipRenderer();
			}

			return tr;
		}
	}

	protected class OrderedIcon implements OrderedRenderable, Locatable {
		protected WWIcon icon;
		protected Vec4 point;
		protected double eyeDistance;
		protected double horizonDistance;
		protected Layer layer;

		public OrderedIcon(WWIcon icon, Vec4 point, Layer layer, double eyeDistance, double horizonDistance) {
			this.icon = icon;
			this.point = point;
			this.eyeDistance = eyeDistance;
			this.horizonDistance = horizonDistance;
			this.layer = layer;
		}

		public double getDistanceFromEye() {
			return this.eyeDistance;
		}

		public Position getPosition() {
			return this.icon.getPosition();
		}

		public IconRenderer getRenderer() {
			return IconRenderer.this;
		}

		public Vec4 getPoint() {
			return this.point;
		}

		public WWIcon getIcon() {
			return this.icon;
		}

		public double getHorizonDistance() {
			return horizonDistance;
		}

		public Layer getLayer() {
			return layer;
		}

		public void render(DrawContext dc) {
			IconRenderer.this.beginDrawIcons(dc);

			try {
				IconRenderer.this.drawIconsInBatch(dc, this);
			} catch (WWRuntimeException e) {
				Logging.error("generic.ExceptionWhileRenderingIcon", e);
			} catch (Exception e) {
				Logging.error("generic.ExceptionWhileRenderingIcon", e);
			} finally {
				IconRenderer.this.endDrawIcons(dc);
			}
		}

		public void pick(DrawContext dc, Point pickPoint) {
			IconRenderer.this.pickSupport.clearPickList();
			IconRenderer.this.beginDrawIcons(dc);
			try {
				if (IconRenderer.this.isAllowBatchPicking())
					IconRenderer.this.pickIconsInBatch(dc, this);
				else
					IconRenderer.this.drawIcon(dc, this);
			} catch (WWRuntimeException e) {
				Logging.error("generic.ExceptionWhileRenderingIcon", e);
			} catch (Exception e) {
				Logging.error("generic.ExceptionWhilePickingIcon", e);
			} finally {
				IconRenderer.this.endDrawIcons(dc);
				IconRenderer.this.pickSupport.resolvePick(dc, pickPoint, layer);
				IconRenderer.this.pickSupport.clearPickList(); // to ensure
																// entries can
																// be garbage
																// collected
			}
		}
	}

	protected void beginDrawIcons(DrawContext dc) {
		// GL gl = dc.getGL();

		// this.oglStackHandler.clear();

		// int attributeMask =
		// GL.GL_DEPTH_BUFFER_BIT // for depth test, depth mask and depth func
		// | GL.GL_TRANSFORM_BIT // for modelview and perspective
		// | GL.GL_VIEWPORT_BIT // for depth range
		// | GL.GL_CURRENT_BIT // for current color
		// | GL.GL_COLOR_BUFFER_BIT // for alpha test func and ref, and blend
		// | GL.GL_DEPTH_BUFFER_BIT // for depth func
		// | GL.GL_ENABLE_BIT; // for enable/disable changes
		// this.oglStackHandler.pushAttrib(gl, attributeMask);

		// Apply the depth buffer but don't change it.
		if ((!dc.isDeepPickingEnabled()))
			GLES20.glEnable(GLES20.GL_DEPTH_TEST);
		GLES20.glDepthMask(false);

		// Suppress any fully transparent image pixels
		// gl.glEnable(GL.GL_ALPHA_TEST);
		// gl.glAlphaFunc(GL.GL_GREATER, 0.001f);

		// Load a parallel projection with dimensions (viewportWidth,
		// viewportHeight)
		// this.oglStackHandler.pushProjectionIdentity(gl);
		projection = Matrix.fromIdentity().setOrthographic(0d, dc.getView().getViewport().width, 0d,
				dc.getView().getViewport().height, -1d, 1d);
		// gl.glOrtho(0d, dc.getView().getViewport().width, 0d,
		// dc.getView().getViewport().height, -1d, 1d);

		// this.oglStackHandler.pushModelview(gl);
		// this.oglStackHandler.pushTexture(gl);

		if (dc.isPickingMode()) {
			// this.pickSupport.beginPicking(dc);

			// Set up to replace the non-transparent texture colors with the
			// single pick color.
			GLES20.glEnable(GLES20.GL_TEXTURE_2D);
			// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE,
			// GL.GL_COMBINE);
			// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB, GL.GL_PREVIOUS);
			// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB,
			// GL.GL_REPLACE);
		} else {
			GLES20.glEnable(GLES20.GL_TEXTURE_2D);
			GLES20.glEnable(GLES20.GL_BLEND);
			GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
		}
	}

	protected void endDrawIcons(DrawContext dc) {
		// if (dc.isPickingMode())
		// this.pickSupport.endPicking(dc);

		// GL gl = dc.getGL();

		// if (dc.isPickingMode())
		// {
		// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE,
		// OGLUtil.DEFAULT_TEX_ENV_MODE);
		// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_SRC0_RGB,
		// OGLUtil.DEFAULT_SRC0_RGB);
		// gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_COMBINE_RGB,
		// OGLUtil.DEFAULT_COMBINE_RGB);
		// }

		GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
		GLES20.glDisable(GLES20.GL_TEXTURE_2D);
		GLES20.glDepthMask(true);

		// this.oglStackHandler.pop(dc.getGL());
	}

	protected void drawIconsInBatch(DrawContext dc, OrderedIcon uIcon) {
		this.drawIcon(dc, uIcon);

		// Draw as many as we can in a batch to save ogl state switching.
		Object nextItem = dc.peekOrderedRenderables();
		while (nextItem != null && nextItem instanceof OrderedIcon) {
			OrderedIcon oi = (OrderedIcon) nextItem;
			if (oi.getRenderer() != this)
				return;

			dc.pollOrderedRenderables(); // take it off the queue
			this.drawIcon(dc, oi);

			nextItem = dc.peekOrderedRenderables();
		}
	}

	protected void pickIconsInBatch(DrawContext dc, OrderedIcon uIcon) {
		this.drawIcon(dc, uIcon);

		// Draw as many as we can in a batch to save ogl state switching.
		// Note that there's a further qualification here than in render(): only
		// items associated with the
		// same layer can be batched because the pick resolution step at the end
		// of batch rendering
		// associates the item's layer with the resolved picked object.
		Object nextItem = dc.peekOrderedRenderables();
		while (nextItem != null && nextItem instanceof OrderedIcon && ((OrderedIcon) nextItem).layer == uIcon.layer) {
			OrderedIcon oi = (OrderedIcon) nextItem;
			if (oi.getRenderer() != this)
				return;

			dc.pollOrderedRenderables(); // take it off the queue
			this.drawIcon(dc, oi);

			nextItem = dc.peekOrderedRenderables();
		}
	}

	protected Vec4 drawIcon(DrawContext dc, OrderedIcon uIcon) {
		if (uIcon.point == null) {
			String msg = Logging.getMessage("nullValue.PointIsNull");
			Logging.error(msg);

			// Record feedback data for this WWIcon if feedback is enabled.
			if (uIcon.icon != null)
				this.recordFeedback(dc, uIcon.icon, null, null);

			return null;
		}

		WWIcon icon = uIcon.icon;
		if (dc.getView().getFrustumInModelCoordinates().getNear().distanceTo(uIcon.point) < 0) {
			// Record feedback data for this WWIcon if feedback is enabled.
			this.recordFeedback(dc, icon, uIcon.point, null);

			return null;
		}

		Vec4 screenPoint = new Vec4();
		boolean success = dc.getView().project(uIcon.point, screenPoint);
		if (!success) {
			// Record feedback data for this WWIcon if feedback is enabled.
			this.recordFeedback(dc, icon, uIcon.point, null);

			return null;
		}

		double pedestalScale;
		double pedestalSpacing;
		if (this.pedestal != null) {
			pedestalScale = this.pedestal.getScale();
			pedestalSpacing = pedestal.getSpacingPixels();
		} else {
			pedestalScale = 0d;
			pedestalSpacing = 0d;
		}

		// javax.media.opengl.GL gl = dc.getGL();

		this.setDepthFunc(dc, uIcon, screenPoint);

		Matrix modelview = Matrix.fromIdentity();
		// gl.glMatrixMode(GL.GL_MODELVIEW);
		// gl.glLoadIdentity();

		Rect size = icon.getSize();
		double width = size != null ? size.width : icon.getImageTexture().getWidth(dc);
		double height = size != null ? size.height : icon.getImageTexture().getHeight(dc);
		modelview.multiplyAndSet(Matrix.fromTranslation(screenPoint.x - width / 2, screenPoint.y
				+ (pedestalScale * height) + pedestalSpacing, 0d));
		// gl.glTranslated(screenPoint.x - width / 2, screenPoint.y +
		// (pedestalScale * height) + pedestalSpacing, 0d);

		if (icon.isHighlighted()) {
			double heightDelta = this.pedestal != null ? 0 : height / 2; // expand
																			// only
																			// above
																			// the
																			// pedestal
			modelview.multiplyAndSet(Matrix.fromTranslation(width / 2, heightDelta, 0));
			// gl.glTranslated(width / 2, heightDelta, 0);
			modelview.multiplyAndSet(Matrix.fromScale(icon.getHighlightScale(), icon.getHighlightScale(),
					icon.getHighlightScale()));
			// gl.glScaled(icon.getHighlightScale(), icon.getHighlightScale(),
			// icon.getHighlightScale());
			modelview.multiplyAndSet(Matrix.fromTranslation(-width / 2, -heightDelta, 0));
			// gl.glTranslated(-width / 2, -heightDelta, 0);
		}

		Rect rect = new Rect((int) (screenPoint.x - width / 2), (int) (screenPoint.y), (int) width, (int) (height
				+ (pedestalScale * height) + pedestalSpacing));

		int pickColor = 0;
		if (dc.isPickingMode()) {
			// If in picking mode and pick clipping is enabled, check to see if
			// the icon is within the pick volume.
			// TODO - BBB add pick frustum clipping support
			// if (this.isPickFrustumClippingEnabled() &&
			// !dc.getPickFrustums().intersectsAny(rect))
			// {
			// // Record feedback data for this WWIcon if feedback is enabled.
			// this.recordFeedback(dc, icon, uIcon.point, rect);
			//
			// return screenPoint;
			// }
			// else
			// {
			 pickColor = dc.getUniquePickColor();
//			 int colorCode = Color.rgb(Color.red(pickColor), Color.green(pickColor), Color.blue(pickColor));
			 this.pickSupport.addPickableObject(pickColor, icon,
			 uIcon.getPosition(), false);
//			 gl.glColor3ub((byte) color.getRed(), (byte) color.getGreen(),
//			 (byte) color.getBlue());
			// }
		}

		GLES20.glActiveTexture(0);

		if (icon.getBackgroundTexture() != null)
			this.applyBackground(dc, icon, screenPoint, width, height, pedestalSpacing, pedestalScale);

		if (icon.getImageTexture().bind(dc)) {
			float[] texCoords = icon.getImageTexture().getTexCoords();
			modelview.multiplyAndSet(Matrix.fromScale(width, height, 1d));
			// gl.glScaled(width, height, 1d);
			if (dc.isPickingMode()) {
				drawRect(dc, projection, modelview, pickColor);
			} else {
				drawUnitQuad(dc, texCoords, projection, modelview, 0);
			}
		}

		if (this.pedestal != null && this.pedestal.getImageTexture() != null) {
			modelview = Matrix.fromIdentity();
			// gl.glLoadIdentity();
			modelview.multiplyAndSet(Matrix.fromTranslation(screenPoint.x - (pedestalScale * (width / 2)),
					screenPoint.y, 0d));
			// gl.glTranslated(screenPoint.x - (pedestalScale * (width / 2)),
			// screenPoint.y, 0d);
			modelview.multiplyAndSet(Matrix.fromScale(width * pedestalScale, height * pedestalScale, 1d));
			// gl.glScaled(width * pedestalScale, height * pedestalScale, 1d);

			if (this.pedestal.getImageTexture().bind(dc)) {
				float[] texCoords = this.pedestal.getImageTexture().getTexCoords();
				drawUnitQuad(dc, texCoords, projection, modelview, 0);
			}
		}

		// Record feedback data for this WWIcon if feedback is enabled.
		this.recordFeedback(dc, icon, uIcon.point, rect);

		return screenPoint;
	}

	private void drawRect(DrawContext dc, Matrix projectionMatrix, Matrix modelviewMatrix, int pickColor) {
		Matrix mvp = Matrix.fromIdentity().multiplyAndSet(projectionMatrix, modelviewMatrix);
		GpuProgram colorProgram = this.getGpuProgram(dc.getGpuResourceCache(), programColorKey,
				VERTEX_SHADER_PATH_COLOR, FRAGMENT_SHADER_PATH_COLOR);
		if (colorProgram != null){
			colorProgram.bind();
			colorProgram.loadUniformMatrix("mvpMatrix", mvp);
			colorProgram.loadUniformVec4("uColor", Color.red(pickColor)/255.0, Color.green(pickColor)/255.0, Color.blue(pickColor)/255.0, 1);
			float[] unitQuadVerts = new float[] { 0, 0, 1, 0, 1, 1, 0, 1 };
			int pointLocation = colorProgram.getAttribLocation("vertexPoint");
			GLES20.glEnableVertexAttribArray(pointLocation);
			FloatBuffer vertBuf = createBuffer(unitQuadVerts);
			GLES20.glVertexAttribPointer(pointLocation, 2, GLES20.GL_FLOAT, false, 0, vertBuf);
			GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, unitQuadVerts.length / 2);
			GLES20.glDisableVertexAttribArray(pointLocation);
		}
	}

	protected void drawUnitQuad(DrawContext dc, float[] texCoords, Matrix projectionMatrix, Matrix modelviewMatrix,
			int activeTexture) {
		Matrix mvp = Matrix.fromIdentity().multiplyAndSet(projectionMatrix, modelviewMatrix);
		GpuProgram textureProgram = this.getGpuProgram(dc.getGpuResourceCache(), programTextureKey,
				VERTEX_SHADER_PATH_TEXTURE, FRAGMENT_SHADER_PATH_TEXTURE);
		if (textureProgram != null) {
			textureProgram.bind();
			textureProgram.loadUniformMatrix("mvpMatrix", mvp);
			textureProgram.loadUniformSampler("sTexture", 0);
			float[] unitQuadVerts = new float[] { 0, 0, 1, 0, 1, 1, 0, 1 };
			int pointLocation = textureProgram.getAttribLocation("vertexPoint");
			GLES20.glEnableVertexAttribArray(pointLocation);
			FloatBuffer vertBuf = createBuffer(unitQuadVerts);
			GLES20.glVertexAttribPointer(pointLocation, 2, GLES20.GL_FLOAT, false, 0, vertBuf);
			int textureLocation = textureProgram.getAttribLocation("aTextureCoord");
			GLES20.glEnableVertexAttribArray(textureLocation);
			FloatBuffer textureBuf = createBuffer(texCoords);
			GLES20.glVertexAttribPointer(textureLocation, 2, GLES20.GL_FLOAT, false, 0, textureBuf);
			GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, unitQuadVerts.length / 2);
			GLES20.glDisableVertexAttribArray(pointLocation);
			GLES20.glDisableVertexAttribArray(textureLocation);
		}
	}

	protected FloatBuffer createBuffer(float[] array) {
		FloatBuffer retval = ByteBuffer.allocateDirect(array.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		retval.put(array);
		retval.rewind();
		return retval;
	}

	protected GpuProgram getGpuProgram(GpuResourceCache cache, Object programKey, String shaderPath, String fragmentPath) {

		GpuProgram program = cache.getProgram(programKey);

		if (program == null) {
			try {
				GpuProgram.GpuProgramSource source = GpuProgram.readProgramSource(shaderPath, fragmentPath);
				program = new GpuProgram(source);
				cache.put(programKey, program);
			} catch (Exception e) {
				String msg = Logging.getMessage("GL.ExceptionLoadingProgram", shaderPath, fragmentPath);
				Logging.error(msg);
			}
		}

		return program;
	}

	protected void applyBackground(DrawContext dc, WWIcon icon, Vec4 screenPoint, double width, double height,
			double pedestalSpacing, double pedestalScale) {
		// javax.media.opengl.GL gl = dc.getGL();
		//
		// double backgroundScale;
		// backgroundScale = icon.getBackgroundScale();
		//
		// if (icon.getBackgroundTexture() != null)
		// {
		// if (icon.getBackgroundTexture().bind(dc))
		// {
		// TextureCoords texCoords = icon.getBackgroundTexture().getTexCoords();
		// gl.glPushMatrix();
		// gl.glLoadIdentity();
		// double bgwidth = backgroundScale * width;
		// double bgheight = backgroundScale * height;
		// // Offset the background for the highlighted scale.
		// //if (icon.isHighlighted())
		// //{
		// // gl.glTranslated(0d, height * (icon.getHighlightScale() - 1) / 2,
		// 0d);
		// //}
		// // Offset the background for the pedestal height.
		// gl.glTranslated(0d, (pedestalScale * height) + pedestalSpacing, 0d);
		// // Place the background centered behind the icon.
		// gl.glTranslated(screenPoint.x - bgwidth / 2, screenPoint.y -
		// (bgheight - height) / 2, 0d);
		// // Scale to the background image dimension.
		// gl.glScaled(bgwidth, bgheight, 1d);
		// dc.drawUnitQuad(texCoords);
		// gl.glPopMatrix();
		// }
		// }
	}

	protected void setDepthFunc(DrawContext dc, OrderedIcon uIcon, Vec4 screenPoint) {
		// GL gl = dc.getGL();

		if (uIcon.icon.isAlwaysOnTop()) {
			GLES20.glDepthFunc(GLES20.GL_ALWAYS);
			return;
		}

		Position eyePos = dc.getView().getEyePosition(dc.getGlobe());
		if (eyePos == null) {
			GLES20.glDepthFunc(GLES20.GL_ALWAYS);
			return;
		}

		double altitude = eyePos.elevation;
		if (altitude < (dc.getGlobe().getMaxElevation() * dc.getVerticalExaggeration())) {
			double depth = screenPoint.z - (8d * 0.00048875809d);
			depth = depth < 0d ? 0d : (depth > 1d ? 1d : depth);
			GLES20.glDepthFunc(GLES20.GL_LESS);
			GLES20.glDepthRangef((float) depth, (float) depth);
		} else if (uIcon.eyeDistance > uIcon.horizonDistance) {
			GLES20.glDepthFunc(GLES20.GL_EQUAL);
			GLES20.glDepthRangef(1f, 1f);
		} else {
			GLES20.glDepthFunc(GLES20.GL_ALWAYS);
		}
	}

	@Override
	public String toString() {
		return Logging.getMessage("layers.IconLayer.Name");
	}

	// **************************************************************//
	// ******************** Feedback ******************************//
	// **************************************************************//

	/**
	 * Returns true if the IconRenderer should record feedback about how the
	 * specified WWIcon has been processed.
	 * 
	 * @param dc
	 *            the current DrawContext.
	 * @param icon
	 *            the WWIcon to record feedback information for.
	 * 
	 * @return true to record feedback; false otherwise.
	 */
	protected boolean isFeedbackEnabled(DrawContext dc, WWIcon icon) {
		if (dc.isPickingMode())
			return false;

		Boolean b = (Boolean) icon.getValue(AVKey.FEEDBACK_ENABLED);
		return (b != null && b);
	}

	/**
	 * If feedback is enabled for the specified WWIcon, this method records
	 * feedback about how the specified WWIcon has been processed.
	 * 
	 * @param dc
	 *            the current DrawContext.
	 * @param icon
	 *            the icon which the feedback information refers to.
	 * @param modelPoint
	 *            the icon's reference point in model coordinates.
	 * @param screenRect
	 *            the icon's bounding rectangle in screen coordinates.
	 */
	protected void recordFeedback(DrawContext dc, WWIcon icon, Vec4 modelPoint, Rect screenRect) {
		if (!this.isFeedbackEnabled(dc, icon))
			return;

		this.doRecordFeedback(dc, icon, modelPoint, screenRect);
	}

	/**
	 * Records feedback about how the specified WWIcon has been processed.
	 * 
	 * @param dc
	 *            the current DrawContext.
	 * @param icon
	 *            the icon which the feedback information refers to.
	 * @param modelPoint
	 *            the icon's reference point in model coordinates.
	 * @param screenRect
	 *            the icon's bounding rectangle in screen coordinates.
	 */
	@SuppressWarnings({ "UnusedDeclaration" })
	protected void doRecordFeedback(DrawContext dc, WWIcon icon, Vec4 modelPoint, Rect screenRect) {
		icon.setValue(AVKey.FEEDBACK_REFERENCE_POINT, modelPoint);
		icon.setValue(AVKey.FEEDBACK_SCREEN_BOUNDS, screenRect);
	}
}
